package log

import (
	"io"
	"log"
	"os"
	"runtime"
	"sync"
	"time"

	"xp/lib/beanstalk"
	"xp/lib/etc"
	"xp/lib/jsonx"
	"xp/lib/md"
)

const (
	maxLogBufferSize = 1024
)

var (
	dataPool = &sync.Pool{
		New: func() interface{} {
			return &Data{
				Host:     md.GetHost(),
				Port:     md.Get().Host.Port,
				Service:  md.GetService(),
				Revision: md.Get().VersionControl.Revision,
			}
		},
	}
)

type Data struct {
	Host      string    `json:"_host"`
	Port      int       `json:"_port,string"`
	Level     string    `json:"_level"`
	Service   string    `json:"_service"`
	Revision  string    `json:"_revision"`
	Timestamp time.Time `json:"_timestamp"`
	Fields    []string  `json:"fields"`
}

func (v *Data) Marshal(buf []byte) (int, error) {
	var i int

	var tb [35]byte
	i += copy(buf[i:], `{"_host":"`)
	i += copy(buf[i:], v.Host)
	i += copy(buf[i:], `","_port":"`)
	i += jsonx.FormatBits(buf[i:], uint64(v.Port), v.Port < 0)
	i += copy(buf[i:], `","_caller":"`)

	var pcs = make([]uintptr, 2)
	runtime.Callers(4, pcs)
	i += writeCallers(pcs, buf[i:])
	i += copy(buf[i:], `","_level":"`)
	i += copy(buf[i:], v.Level)
	i += copy(buf[i:], `","_service":"`)
	i += copy(buf[i:], v.Service)
	i += copy(buf[i:], `","_revision":"`)
	i += copy(buf[i:], v.Revision)
	i += copy(buf[i:], `","_timestamp":"`)
	i += copy(buf[i:], v.Timestamp.AppendFormat(tb[:0], time.RFC3339))
	i += copy(buf[i:], `","fields":{`)
	if len(v.Fields) > 0 {
		for j := 0; j < len(v.Fields); j += 2 {
			i += copy(buf[i:], `"`)
			i += copy(buf[i:], v.Fields[j])
			i += copy(buf[i:], `":"`)
			i += jsonx.AppendString(buf[i:], v.Fields[j+1])
			i += copy(buf[i:], `",`)
		}
		i -= 1
	}
	i += copy(buf[i:], `}}`)
	return i, nil
}

type logger struct {
	io.Writer

	level string
}

func New(level string) *logger {
	var writers []io.Writer
	if etc.ModeDev() {
		writers = append(writers, stderr{})
	}
	writers = append(writers, beanstalk.NewProducer(sockets, tube))
	return &logger{
		level:  level,
		Writer: io.MultiWriter(writers...),
	}
}

func (l logger) keyvals(kvs ...string) {
	rawD := dataPool.Get()
	d := rawD.(*Data)
	if len(kvs)%2 != 0 {
		kvs = append(kvs, "<unknown>")
	}
	d.Timestamp = time.Now()
	d.Fields = kvs
	d.Level = l.level
	buf := make([]byte, maxLogBufferSize)
	n, _ := d.Marshal(buf)
	if n == maxLogBufferSize {
		log.Printf("logging size overflow: %+v\n", d)
		return
	}
	if _, err := l.Write(buf[:n]); err != nil {
		log.Printf("logging write failed: %+v\n", err)
	}
	d.Fields = nil
	dataPool.Put(rawD)
}

var (
	info  = New("info")
	warn  = New("warn")
	fatal = New("fatal")
	trace = New("trace")
)

func Info(kvs ...string) {
	info.keyvals(kvs...)
}

func Warn(kvs ...string) {
	warn.keyvals(kvs...)
}

func Fatal(kvs ...string) {
	fatal.keyvals(kvs...)
	os.Exit(1)
}

func Trace(kvs ...string) {
	trace.keyvals(kvs...)
}
